package handlers

import (
	"context"
	"fmt"
	"math/rand"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"

	"github.com/deja-v-u/hzk"
	"go.uber.org/zap"
	"missevanbot/config"
	"missevanbot/models"
	"missevanbot/modules"
	"missevanbot/modules/thirdparty"
	"missevanbot/utils"
)

// cmdHandler is the function type that receives *command
// and handle the command event.
type cmdHandler struct {
	fn   func(cmd *models.Command)
	role int
}

var _cmdMap = map[int]cmdHandler{
	models.CmdBotHelper:   {cmdBotHelper, models.RoleMember},
	models.CmdBotManual:   {cmdBotManual, models.RoleMember},
	models.CmdBotFeatures: {cmdBotFeatures, models.RoleMember},
	models.CmdBotFeedback: {cmdBotFeedback, models.RoleMember},
	models.CmdRoomInfo:    {cmdRoomInfo, models.RoleAdmin},
	models.CmdRoomAdmin:   {cmdRoomAdmin, models.RoleCreator},
	models.CmdRoomStats:   {cmdRoomStats, models.RoleCreator},
	models.CmdRoomBgImage: {cmdRoomBgImage, models.RoleMember},
	models.CmdTextBoard:   {cmdTextBoard, models.RoleAdmin},
	models.CmdNickname:    {cmdNickname, models.RoleMember},
	models.CmdWhoami:      {cmdWhoami, models.RoleMember},
	models.CmdCheckin:     {cmdCheckin, models.RoleMember},
	models.CmdCheckinRank: {cmdCheckinRank, models.RoleMember},
	models.CmdSlackOff:    {cmdSlackOff, models.RoleMember},
	models.CmdHoroscope:   {cmdHoroscope, models.RoleMember},
	models.CmdWeather:     {cmdWeather, models.RoleMember},
	models.CmdTiangou:     {cmdTiangou, models.RoleMember},
	models.CmdSongReq:     {cmdSongReq, models.RoleMember},
	models.CmdSongList:    {cmdSongList, models.RoleAdmin},
	models.CmdSongDone:    {cmdSongDone, models.RoleAdmin},
	models.CmdSongClear:   {cmdSongClear, models.RoleAdmin},
	models.CmdPiaStart:    {cmdPiaStart, models.RoleAdmin},
	models.CmdPiaNext:     {cmdPiaNext, models.RoleAdmin},
	models.CmdPiaStop:     {cmdPiaStop, models.RoleAdmin},
	models.CmdModeAll:     {cmdModeAll, models.RoleAdmin},
	models.CmdModeMute:    {cmdModeMute, models.RoleCreator},
	models.CmdModePander:  {cmdModePander, models.RoleAdmin},
	models.CmdModePinyin:  {cmdModePinyin, models.RoleAdmin},
	models.CmdModeNoble:   {cmdModeNoble, models.RoleAdmin},
	models.CmdModeMedal:   {cmdModeMedal, models.RoleAdmin},
	models.CmdModeWater:   {cmdModeWater, models.RoleAdmin},
	models.CmdRollDice:    {cmdRollDice, models.RoleMember},
	models.CmdChar:        {cmdChar, models.RoleMember},
	models.CmdLove:        {cmdLove, models.RoleMember},
}

// cmdBotHelper outputs the main help text by default,
// and outputs other help texts according to the arguments.
func cmdBotHelper(cmd *models.Command) {
	args := cmd.Args
	var key string
	if len(args) == 0 {
		key = modules.TmplHelper
	} else {
		switch args[0] {
		case "点歌", "歌单", "req", "list":
			key = modules.TmplHelperPlaylist
		case "pia戏", "pia":
			key = modules.TmplHelperPia
		case "游戏", "game":
			key = modules.TmplHelperGame
		}
	}

	text, err := modules.NewTemplate(key, nil)
	if err != nil {
		zap.S().Warn(cmd.Log("create bot helper template failed", err))
		return
	}
	cmd.Output <- text
}

func cmdBotManual(cmd *models.Command) {
	cmd.Output <- "https://upyun.secriy.com/missevan/BotHelper.html"
}

func cmdBotFeatures(cmd *models.Command) {
	text, err := modules.NewTemplate(modules.TmplFeatures, nil)
	if err != nil {
		zap.S().Warn(cmd.Log("create bot features template failed", err))
		return
	}
	cmd.Output <- text
}

func cmdBotFeedback(cmd *models.Command) {
	user := cmd.User
	room := cmd.Room

	s := strings.SplitN(strings.TrimSpace(cmd.Raw), " ", 2)
	if len(s) < 2 {
		cmd.Output <- illegal(user.Username, "未输入反馈内容")
		return
	}
	modules.NewFeedback(room, user, s[1])
	cmd.Output <- fmt.Sprintf(models.TplFeedback, user.Username)
}

// The cmdRoomInfo handles the live room information fetching command.
func cmdRoomInfo(cmd *models.Command) {
	info := cmd.Info
	room := info.Room
	creator := info.Creator

	openTime := time.UnixMilli(info.Room.Status.OpenTime)
	dur := time.Since(openTime)

	var lucky string
	sl := modules.StatsLucky(cmd.ID)
	if l, g := sl["lucky"], sl["gift"]; l != 0 && g != 0 {
		lucky = utils.Itoa(int64(float64(g) / float64(l) * 100))
	}

	data := struct {
		Name         string
		Creator      string
		Followers    int64
		Platform     string
		Online       int64
		Accumulation int64
		Count        int64
		Duration     int64
		Vip          int64
		Medal        string
		Lucky        string
	}{
		Name:         room.Name,
		Creator:      creator.Username,
		Followers:    room.Statistics.AttentionCount,
		Platform:     room.Status.Channel.Platform,
		Online:       cmd.Online,
		Accumulation: room.Statistics.Accumulation,
		Count:        cmd.Count,
		Duration:     int64(dur.Minutes()),
		Vip:          info.Room.Statistics.Vip,
		Medal:        room.Medal.Name,
		Lucky:        lucky,
	}

	text, err := modules.NewTemplate(modules.TmplRoomInfo, data)
	if err != nil {
		zap.S().Warn(cmd.Log("create room info template failed", err))
		return
	}
	cmd.Output <- text
}

func cmdRoomAdmin(cmd *models.Command) {
	info := cmd.Info
	fmAdmins := info.Room.Members.Admin

	// The admins slice only contains the name of
	// each live room admin.
	admins := make([]string, 0, len(fmAdmins))
	for _, v := range fmAdmins {
		admins = append(admins, v.Username)
	}

	text, err := modules.NewTemplate(modules.TmplRoomAdmin, admins)
	if err != nil {
		zap.S().Warn(cmd.Log("create room admin template failed", err))
		return
	}
	cmd.Output <- text
}

func cmdRoomStats(cmd *models.Command) {
	rid := cmd.ID

	events := modules.StatsEvent(rid)
	tasks := modules.StatsTask(rid)

	data := struct {
		Alias     string
		Open      int64
		Start     int64
		Ask       int64
		Answer    int64
		JoinQueue int64
		New       int64
		Send      int64
		Followed  int64
		Water     int64
		Pander    int64
	}{
		Alias:     cmd.Alias,
		Open:      events[models.EventOpen],
		Start:     events[models.EventStart],
		Ask:       events[models.EventAsk],
		Answer:    events[models.EventAnswer],
		JoinQueue: events[models.EventJoinQueue],
		New:       events[models.EventNew],
		Send:      events[models.EventSend],
		Followed:  events[models.EventFollowed],
		Water:     tasks[modules.ModeWater],
		Pander:    tasks[modules.ModePander],
	}

	text, err := modules.NewTemplate(modules.TmplStats, data)
	if err != nil {
		zap.S().Warn(cmd.Log("create room stats template failed", err))
		return
	}
	cmd.Output <- text
}

func cmdRoomBgImage(cmd *models.Command) {
	_url := cmd.Info.Room.Background.ImageURL
	cmd.Output <- _url
}

func cmdNickname(cmd *models.Command) {
	args := cmd.Args
	user := cmd.User
	if len(args) == 0 {
		// Set the cmdNickname to empty string to unset the cmdNickname.
		modules.SetNickname(user.UserID, "")
		cmd.Output <- fmt.Sprintf(models.TplNicUnset, user.Username)
		return
	}
	if utf8.RuneCountInString(args[0]) > 12 && cmd.Role > models.RoleBigBrother {
		// Limit the length of user nickname except the RoleBigBrother user.
		cmd.Output <- illegal(user.Username, "长度过长")
		return
	}
	modules.SetNickname(user.UserID, args[0])
	cmd.Output <- fmt.Sprintf(models.TplNicSet, user.Username)
}

func cmdWhoami(cmd *models.Command) {
	userInfo, err := modules.UserInfo(cmd.User.UserID)
	if err != nil {
		cmd.Output <- models.TplSthWrong
		return
	}

	text, err := modules.NewTemplate(modules.TmplWhoami, userInfo)
	if err != nil {
		zap.S().Warn(cmd.Log("create cmdWhoami template failed", err))
		return
	}
	cmd.Output <- text
}

func cmdTextBoard(cmd *models.Command) {
	args := cmd.Args

	if len(args) == 0 {
		cmd.Output <- modules.TextBoard(modules.BoardCommon)
		return
	}

	var board string
	switch v := args[0]; v {
	case "唱歌", "唱", "歌", "s":
		board = modules.BoardSinging
	case "彩虹", "彩红屁", "咳咳", "k":
		board = modules.BoardPander
	case "爱", "心", "表白", "l":
		board = modules.BoardLove
	default:
		board = v
	}
	cmd.Output <- modules.TextBoard(board)
}

// The cmdCheckin handles the cmdCheckin command.
func cmdCheckin(cmd *models.Command) {
	user := cmd.User
	uid, name := user.UserID, user.Username

	text := strings.Builder{}
	if modules.Checkin(cmd.ID, uid, name) {
		text.WriteString(fmt.Sprintf(models.TplSignSuccess, name))
	} else {
		text.WriteString(fmt.Sprintf(models.TplSignDuplicate, name))
	}

	stats := modules.CheckinStats(cmd.ID, uid)
	text.WriteString(fmt.Sprintf("\n累计签到：%d天\n连续签到：%d天\n%s",
		stats.Total,
		stats.Consec,
		stats.Luck,
	))

	cmd.Output <- text.String()
}

func cmdCheckinRank(cmd *models.Command) {
	if rank := modules.CheckinRank(cmd.ID); rank != "" {
		cmd.Output <- fmt.Sprintf("每日签到榜单：%s", rank)
		return
	}
	cmd.Output <- fmt.Sprintf(models.TplRankEmpty, cmd.User.Username)
}

func cmdSlackOff(cmd *models.Command) {
	// TODO: Add some choices.
}

// TODO: find a new API to represent this function.
func cmdHoroscope(cmd *models.Command) {
	args := cmd.Args
	if len(args) == 0 {
		cmd.Output <- illegal(cmd.User.Username, "未输入星座名")
		return
	}

	str := args[0]
	if len(str) == 6 {
		str += "座"
	}
	if _, ok := thirdparty.StarList[str]; !ok {
		return
	}

	ctx := context.Background()
	rdb := config.RDB
	key := modules.Prefix + fmt.Sprintf("zodiac:%s:%s", utils.Today(), str)

	n, err := rdb.Exists(ctx, key).Result()
	if err != nil {
		zap.S().Warn(cmd.Log("get horoscopes failed", err))
		return
	}
	if n > 0 {
		ret := rdb.HMGet(ctx, key, "content", "score")
		cmd.Output <- fmt.Sprintf(models.TplStarFortune, str, ret.Val()[1], ret.Val()[0])
	} else {
		// check if exists
		rand.Seed(time.Now().UnixNano())
		level := rand.Intn(5) + 1
		fort, err := thirdparty.Zodiac(str, level)
		if err != nil {
			zap.S().Warn(cmd.Log("get horoscopes failed", err))
			return
		}
		rdb.HMSet(ctx, key, "content", fort.Content, "score", fort.Score)
		cmd.Output <- fmt.Sprintf(models.TplStarFortune, str, fort.Score, fort.Content)
	}
}

func cmdWeather(cmd *models.Command) {
	args := cmd.Args
	if len(args) == 0 {
		cmd.Output <- illegal(cmd.User.Username, "未输入城市名")
		return
	}

	text, err := thirdparty.WeatherText(args[0])
	if err != nil {
		zap.S().Warn(cmd.Log("get weather failed", err))
		return
	}
	cmd.Output <- text
}

func cmdTiangou(cmd *models.Command) {
	text, err := thirdparty.Tiangou()
	if err != nil {
		zap.S().Warn(cmd.Log("fetch cmdTiangou api failed", err))
	}
	cmd.Output <- text
}

// The order is the struct that used for
// storing the song's name and the command sender's name.
type order struct {
	Name string
	User string
}

func cmdSongReq(cmd *models.Command) {
	args := cmd.Args
	if len(args) == 0 {
		cmd.Output <- illegal(cmd.User.Username, "未输入歌曲名")
		return
	}

	name := cmd.User.Username
	list := cmd.Playlist
	song := strings.Join(args, " ")
	list.PushBack(order{song, name})

	cmdSongList(cmd) // Output the songs list immediately.
}

func cmdSongList(cmd *models.Command) {
	list := cmd.Playlist
	ele := list.Front()
	songs := make([]order, 0, 10)
	for i := 0; i < 10 && ele != nil; i++ {
		songs = append(songs, ele.Value.(order))
		ele = ele.Next()
	}

	text, err := modules.NewTemplate(modules.TmplPlaylist, songs)
	if err != nil {
		zap.S().Warn(cmd.Log("create template failed", err))
		return
	}
	cmd.Output <- text
}

func cmdSongDone(cmd *models.Command) {
	args := cmd.Args
	list := cmd.Playlist
	ele := list.Front()
	if len(args) > 0 {
		if n, err := strconv.Atoi(args[0]); err == nil && n <= list.Len() {
			for i := 1; i < n; i++ {
				ele = ele.Next()
			}
		}
	}
	val := list.Remove(ele)
	cmd.Output <- fmt.Sprintf(models.TplSongDone, cmd.User.Username, val.(order).Name)
}

func cmdSongClear(cmd *models.Command) {
	cmd.Playlist.Init()
	cmd.Output <- fmt.Sprintf(models.TplSongClear, cmd.User.Username)
}

func cmdPiaStart(cmd *models.Command) {
	args := cmd.Args
	if len(args) == 0 {
		cmd.Output <- illegal(cmd.User.Username, "未输入本号")
		return
	}
	id, err := utils.Atoi(args[0])
	if err != nil {
		return
	}

	var roles []string
	roles, cmd.PiaParas, err = thirdparty.DramaScript(id)
	if err != nil {
		zap.S().Warn(cmd.Log("get scripts failed", err))
		return
	}
	cmd.PiaIndex = 1

	text := strings.Builder{}
	for _, v := range roles {
		text.WriteString(fmt.Sprintf("\n%s", v))
	}
	cmd.Output <- fmt.Sprintf(models.TplPiaStart, cmd.User.Username, text.String())
}

const defParas = 7 // Default number of the paragraphs.

// cmdPiaNext outputs N paragraph in safety mode.
func cmdPiaNext(cmd *models.Command) {
	args := cmd.Args
	if len(args) == 0 {
		piaNextN(cmd, defParas)
		return
	}
	dur, err := strconv.Atoi(args[0])
	if err != nil {
		return
	}
	piaNextN(cmd, dur)
}

// piaNextN outputs dur paragraphs.
// If safe=true, output paragraphs in safety mode
// that can avoid being blocked.
func piaNextN(cmd *models.Command, dur int) {
	index := cmd.PiaIndex
	list := cmd.PiaParas
	if index == 0 || list == nil {
		// There is no script in PiaParas.
		cmd.Output <- fmt.Sprintf(models.TplPiaEmpty, cmd.User.Username)
		return
	}

	length := len(list)
	start := index - 1
	stop := start + dur
	if stop > length {
		// Index out of bounds, relocate to tail of the list.
		stop = length
	}

	text := strings.Builder{}
	text.WriteString(strings.Join((list)[start:stop], "\n"))
	text.WriteString(fmt.Sprintf("\n\n进度：%d/%d", stop, length))

	cmd.PiaIndex = stop + 1

	if stop == length {
		// If reaching the end, clear the list and index.
		cmd.PiaParas = nil
		cmd.PiaIndex = 0
		text.WriteString("\n\n" + models.TplPiaDone)
	}
	cmd.Output <- text.String()
}

func cmdPiaStop(cmd *models.Command) {
	if cmd.PiaParas == nil {
		return
	}

	cmd.PiaParas = nil
	cmd.PiaIndex = 0
	cmd.Output <- fmt.Sprintf(models.TplPiaStop, cmd.User.Username)
}

func cmdModeAll(cmd *models.Command) {
	data := modules.ModeAll(cmd.ID)
	text, err := modules.NewTemplate(modules.TmplMode, data)
	if err != nil {
		zap.S().Warn(cmd.Log("create template failed", err))
		return
	}
	cmd.Output <- text
}

func cmdModeMute(cmd *models.Command) {
	var text string
	if modules.SwitchMode(cmd.ID, modules.ModeMute) {
		text = models.TplModeEnabled
	} else {
		text = models.TplModeDisabled
	}
	cmd.Output <- fmt.Sprintf(text, cmd.User.Username)
}

func cmdModePinyin(cmd *models.Command) {
	var text string
	if modules.SwitchMode(cmd.ID, modules.ModePinyin) {
		text = models.TplModeEnabled
	} else {
		text = models.TplModeDisabled
	}
	cmd.Output <- fmt.Sprintf(text, cmd.User.Username)
}

func cmdModeNoble(cmd *models.Command) {
	var text string
	if modules.SwitchMode(cmd.ID, modules.ModeNoble) {
		text = models.TplModeEnabled
	} else {
		text = models.TplModeDisabled
	}
	cmd.Output <- fmt.Sprintf(text, cmd.User.Username)
}

func cmdModeMedal(cmd *models.Command) {
	var text string
	if modules.SwitchMode(cmd.ID, modules.ModeMedal) {
		text = models.TplModeEnabled
	} else {
		text = models.TplModeDisabled
	}
	cmd.Output <- fmt.Sprintf(text, cmd.User.Username)
}

func cmdModePander(cmd *models.Command) {
	switchValue(cmd, modules.ModePander)
}

func cmdModeWater(cmd *models.Command) {
	switchValue(cmd, modules.ModeWater)
}

// switchValue handles the switch actions that could have an argument.
func switchValue(cmd *models.Command, key string) {
	args := cmd.Args
	name := cmd.User.Username
	var text string
	if len(args) == 0 {
		if modules.SwitchMode(cmd.ID, key) {
			text = models.TplModeEnabled
		} else {
			text = models.TplModeDisabled
		}
	} else {
		if !modules.Validate(args[0]) {
			cmd.Output <- illegal(name, "")
			return
		}
		modules.SetMode(cmd.ID, key, args[0])
		text = models.TplModeEnabled
	}
	cmd.Output <- fmt.Sprintf(text, name)
}

func cmdRollDice(cmd *models.Command) {
	var count int64 = 1

	args := cmd.Args
	if len(args) > 0 {
		c, err := utils.Atoi(args[0])
		if err == nil {
			count = c
		}
	}
	if count > 5 {
		count = 5
	}
	arr := modules.WordN(modules.WordDice, count)
	cmd.Output <- strings.Join(arr, "\n\n")
}

func cmdChar(cmd *models.Command) {
	args := cmd.Args
	if len(args) == 0 || utf8.RuneCountInString(args[0]) == 0 {
		cmd.Output <- illegal(cmd.User.Username, "")
		return
	}

	rs := []rune(args[0])
	text := strings.Builder{}
	for k, r := range rs {
		str := string(r)
		if extend := modules.ExtendChar(str); extend != "" {
			cmd.Output <- extend
			return
		}

		matrix, err := hzk.Matrix([]byte(str))
		if err != nil {
			cmd.Output <- illegal(cmd.User.Username, "只能为中文字符")
			return
		}
		key := []byte{0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}
		for row := 0; row < 16; row++ {
			for j := 0; j < 2; j++ {
				for i := 0; i < 8; i++ {
					if flag := (matrix[2*row+j] & key[i]) == 0; flag {
						text.WriteString("。")
					} else {
						text.WriteString("回")
					}
				}
			}
			if row < 15 {
				text.WriteString("\n")
			}
		}

		if cmd.Role > models.RoleAdmin || (cmd.Role > models.RoleBigBrother && k >= 9) {
			// RoleMember users can only send one character.
			// RoleAdmin users can only send less than or equal to 10 characters.
			// RoleSuper users have no limit on the number of characters.
			break
		}

		if k < len(rs)-1 {
			text.WriteString("\n\n")
		}
	}
	cmd.Output <- text.String()
}

func cmdLove(cmd *models.Command) {
	cmd.Output <- "❤️~"
}

func illegal(name, reason string) string {
	if reason != "" {
		reason = "（" + reason + "）"
	}
	return fmt.Sprintf(models.TplIllegal, name, reason)
}
